Introduction to Custom Functions in Python

Because copy-pasting code is so last semester

Danilo Freire

Emory University

February 07, 2025

Hello, everyone!
Nice to see you all! 😉

Lecture outline

  • Why Functions?
    • Code Repetition & DRY
  • What are Functions?
    • Reusable Blocks (def)
  • Function Syntax
    • Anatomy & Examples
  • Function Arguments
    • Positional, Keyword, Default
  • Lambda Functions
    • Anonymous, One-liners
  • Variable Scope
    • Local & Global Scope
  • Best Practices & Pitfalls
    • Naming, Docstrings, Indentation, return
  • Summary & Q&A
    • Recap & Next Steps

Let’s get started! 🚀

Why functions?

Why functions?

  • The problem: code repetition is a trap!
    • Imagine writing the same code…
    • … again and again and again! 😩
  • An example: greeting many people
    • Imagine doing this for 1000 names!
  • Error-prone updates
  • Hard to read and maintain
  • Not efficient at all! 🐢
# Greeting people (without functions)
# Don't do this at home!

print("Nice to meet you, Alice!")
print("Nice to meet you, Bob!")
print("Nice to meet you, Charlie!")
print("Nice to meet you, Danilo!")
print("Nice to meet you, Emily!")
print("Nice to meet you, Frank!")
print("Nice to meet you, George!")

# ... repeats 993 more times ...

Maybe there’s a better way to do this? 🤔

Functions to the rescue! 🦸‍♂️

The best way to greet people (and much more!)

  • Functions are like mini-programs:
    • Self-contained blocks of code
    • Designed to perform a specific task
  • “Recipes” for code: 🧑‍🍳
    • You define the steps (code inside the function)
    • You can “call” the function (use the recipe) whenever you need it
  • You know them already!
    • print(), type(), np.mean(), plt.hist() are all functions you’ve used before! 🤓
  • Why are they useful?
    • Reusable code
    • Organised logic
    • One change updates all
# Greeting people (with functions)
# Much better!

def greet(your_name):
    print(f"Nice to meet you, {your_name}!")

greet("Alice")
greet("Bob")
greet("Charlie")
greet("Danilo")
Nice to meet you, Alice!
Nice to meet you, Bob!
Nice to meet you, Charlie!
Nice to meet you, Danilo!

DRY principle: Don’t Repeat Yourself 😉

Anatomy of a Python function

The building blocks

  • Writing a function is like writing a sentence: it has structure 🤓📚

  1. def keyword. Tells Python: “We are defining a function now”
  2. Function Name (function_name). Choose a descriptive name (e.g., greet, calculate_age, etc)
  3. Parentheses (). Enclose inputs the function needs. Empty () means no inputs
  4. Parameter(s) (optional) (parameter). A placeholder for the values the function will receive. Can be multiple, separated by commas
  1. Argument(s) (optional) (parameter=argument). The actual values that will be passed to the function.
  2. Colon : Ends the function header. Don’t forget it!
  3. Function Body. Code to execute. After four spaces!
  4. return statement (optional). What the function sends back. If absent, function returns None. Indent it too!

Let’s see some examples together 🛠️

Hello, Alice and Bob!

  • Function with one parameter: say_greeting(name)
def say_greeting(name):
    print(f"Hello, {name}! Nice to see you.")
  • Think about it:
    • Can you identify the parts we discussed?
    • Does it have a return statement?
    • What if we store the output in a variable, like message_alice = say_greeting("Alice")?
say_greeting("Alice")
Hello, Alice! Nice to see you.
message_alice = say_greeting("Alice")
Hello, Alice! Nice to see you.
type(message_alice)
NoneType

Let’s see some examples together 🛠️

Hello, Alice and Bob!

  • And what about this one?
def get_greeting(name, greeting_type="Hello"):
    message = f"{greeting_type}, {name}! Nice to see you."
    return message
  • Think about it:
    • How is this function different from say_greeting?
    • What about the greeting_type="Hello" part? What does it mean?
    • What if you run: message_bob = get_greeting("Bob") and then print(message)?
# No errors and no output!
message_bob = get_greeting("Bob")
type(message_bob)
str
print(message_bob)
Hello, Bob! Nice to see you.

We can use the result in further calculations!

More about the return statement here

Passing arguments to functions

Positional vs keyword arguments

  • We pass arguments to functions to make them flexible
  • There are two ways to pass arguments: positional and keyword arguments
  • Positional arguments are passed in the order the parameters are defined
  • Let’s see an example:
def get_greeting(name, greeting_type="Hello"):
    message = f"{greeting_type}, {name}! Nice to see you."
    return message

message = get_greeting("Alice", "Hi")
print(message)
Hi, Alice! Nice to see you.

Positional arguments
  • What happens if you switch the order of the arguments?
message = get_greeting("Hi", "Alice")
print(message)
Alice, Hi! Nice to see you.

Order matters! 🚨

Keyword arguments

  • If you use keyword arguments, you can pass arguments by name in any order 😉
  • This is useful when you have many arguments or default values, so you don’t need to remember which ones come first
message = get_greeting(greeting_type="Hi", name="Alice")
print(message)
Hi, Alice! Nice to see you.
  • Now it works just fine! 🎉

  • The same is true for many functions you use often:

import numpy as np

# Default order: loc, scale, size. But you can use keywords!
vector = np.random.normal(size = 4, loc = 10, scale = 2)
print(vector)
[11.3099404  12.02548002  7.12459267  9.65963066]
  • So far, so good? 🤓

Let’s create a function together 🛠️

  • Time to put our knowledge to the test! 🚀
  • Let’s create a function that calculates the area of a rectangle
  • Please complete the calculate_area function below 👇
def calculate_area(_____, _____):   # What parameters do we need?
    # Function body - calculate the area
    ____ = _____ * _____           # How do we calculate area? Assign to 'area' variable
    return ____                    # What should the function return?
  • Then, call the function with length=5 and width=4, and print the result
result = calculate_area(length=5, width=4)    # Call with arguments
print(f"The area is: {result}")                # Check if it's correct!

Another one? 🤓

Oops, something may be wrong here!

  • This function is almost identical to the previous one
  • It looks fine, right? Try to run it and see what happens!
def greet_incorrect(greeting_type="Hello", name):
    message = f"{greeting_type}, {name}!"
    return message
  • What’s the error message? 🚨
  Cell In[14], line 1
    def greet_incorrect(greeting_type="Hello", name): 
                                               ^
SyntaxError: parameter without a default follows parameter with a default
  • What went wrong?
    • Python requires non-default arguments first
    • Fix it by moving name before greeting_type
    • I told you that order matters! 😂
  • Let’s fix it together:
def greet_correct(name, greeting_type="Hello"):
    message = f"{greeting_type}, {name}!"
    return message

message = greet_correct("Alice", "Hi")
print(message)
Hi, Alice!

Phew, that was a lot!

Questions? 🤔

Lambda (anonymous) functions 🕵🏽‍♀️

Lambda functions

  • Sometimes you don’t need a full function definition
  • Just a simple, one-line function is enough
  • Lambda functions are perfect for this!
    • They can have many arguments but only one expression (no return statement)
    • They are also known as anonymous functions, but can be assigned to a variable
  • Syntax: lambda arguments: expression
    • lambda x, y: x + y
  • Let’s calculate x + y + z using a lambda function:
  • Similar to writing a regular function, but in one line!
sum_function = lambda x, y, z: x + y + z

result = sum_function(1, 2, 3)
print(result)
6
  • Easy, right?
  • Now have a look at this 🤯
result = (lambda x, y, z: x + y + z)(1, 2, 3)
print(result)
6

Try it yourself! 🛠️

NASA needs your help! 🚀

  • Exercise: Create a lambda function that calculates travel distance using the formula:
    distance = speed * time
  • Use the lambda function to calculate the distance for speed=100 and time=2
  • Print the result


Great job, everyone! 🎉

Global vs local variables 🌍

Local variables

What happens inside functions stays inside functions! 🤫

  • Python has a concept called variable scope
  • Scope means: Where in your code can you use a variable? 🤔
  • First, let’s understand local variables:
    • They are called local because they are “local” to the function
    • They only exist and can be used within that function’s body
    • They are not accessible from outside the function
  • Think of it like a private workspace:
    • But once the function finishes, that workspace (and its local variables) disappears!
def my_function():
    # message is a local variable
    message = "Hello from inside the function!"
    print(message)

# This works!
my_function() 
Hello from inside the function!
  • Try to access message from outside:
print(message) # Error!
NameError: name 'message' is not defined
  • message has disappeared!
  • Is it clear why? 🤓

Global variables

The world is your oyster (but use them sparingly!) 🌍

  • Now, let’s look at global variables:
    • Variables defined outside of any function (at the top level of your code) are called global variables
    • They can be accessed from anywhere in your code, including inside functions!
  • Think of it like shared information for your whole program:
    • Any part of your code can see and use global variables
# 'global_greeting' is defined outside
global_greeting = "Welcome!" 

# Accessing global variable inside
def greet_function(name):
    print(global_greeting + " " + name) 

greet_function("Alice") # Works fine!
print(global_greeting)   # Works fine outside too!
Welcome! Alice
Welcome!
  • What happens if you try to change a global variable inside a function?
global_greeting = "Welcome!"

def change_greeting(new_greeting):
    global_greeting = new_greeting

change_greeting("Hello!") # No error!
print(global_greeting)    # But it doesn't change!
Welcome!
  • What’s going on here?

The global keyword

When you need to change a global variable inside a function

  • If you want to change a global variable inside a function, you need to use the global keyword

  • This tells Python: “Hey, I’m talking about the global variable here!”

  • Let’s see an example:

global_greeting = "Welcome!"

def change_greeting(new_greeting):
    global global_greeting
    global_greeting = new_greeting

change_greeting("Hello!")
print(global_greeting) # Now it works!
Hello!
  • That’s why we need to be careful about using global variables inside functions
  • They can make your code hard to understand and debug
  • Did you notice how global_greeting is not passed as an argument to the function?
  • And yet we can change its value inside the function!
  • So beware! 🚨
  • Any questions? 🤔

Best practices and pitfalls 🚧

Best practices

How to write better functions

  • Python has a style guide called PEP 8
  • It provides best practices for writing clean, readable code
  • Here are some tips for writing better functions
  • Including some I have learnt the hard way (don’t ask me how)! 😅
  • Naming conventions:
    • Use descriptive names for functions and variables
    • snake_case for functions and variables
    • Avoid special characters and reserved words (e.g., print, sum, list)
  • Bad: def f(x):, def function1():, def sum():, def cR@zY_fUnC(): 👎
  • Good: def calculate_area():, def greet_user():, def get_data(): 👍

Best practices

Use docstrings to document your functions! 📚

  • Docstrings are multi-line comments that describe what your function does
  • They are enclosed in triple quotes (""" or ''')
  • They should include:
    • Function purpose
    • Parameters and return values
    • Examples of how to use the function
    • Additional information if needed
  • They help others (and you!) understand your code
  • Good documentation is a great way to ensure your code is maintainable
def calculate_area(length, width):
    """
    Calculate the area of a rectangle.

    Parameters:
    length (int): The length of the rectangle.
    width (int): The width of the rectangle.

    Returns:
    int: The area of the rectangle.
    """
    area = length * width
    return area
  • That’s beautiful, isn’t it? 😄

Pitfalls to avoid! 🚧

Indentation errors, missing return, and more!

  • You all know that Python is indentation-sensitive
  • It’s not to make your code look pretty, but to define the structure of your code
  • Incorrect indentation = IndentationError!
    • Python will complain loudly if your indentation is wrong 😅
  • Common mistake 1: Forgetting to indent the function body
# No indentation!
def my_function():
print("Hello!")

# Good code!
def my_function():
    print("Hello!")
  • Common mistake 2: Incorrect number of spaces
# Bad code! 2 spaces
def my_function():
  print("Hello!")

# Good code! 4 spaces!
def my_function():
    print("Hello!")
  • Common mistake 3: Mixing spaces and tabs
# Mixing spaces and tabs
def my_function():
        print("Using a tab for indent")
    print("Using spaces")

# Good code! Use spaces only!
def my_function():
    print("Using spaces")

Pitfalls to avoid! 🚧

Return and print: Know the difference! 🤔

  • Return and print are not the same!
  • Return sends a value back to the caller
  • Print displays a value on the screen
  • Common mistake 1: Using print instead of return
def double(x):
    print(x * 2)

# Good code! Use return!
def double(x):
    return x * 2
  • Common mistake 2: Forgetting to return a value when you need one
def double(x):
    x * 2

# Good code! Don't forget to return!
def double(x):
    return x * 2
  • Common mistake 3: Using return inside a loop or if statement
# No return statement for false?
def is_even(number):
    if number % 2 == 0:
        return True

# Good code! Return outside the if statement
def is_even(number):
    if number % 2 == 0:
        return True
    return False

Summary 📚

Summary

  • Functions are like mini-programs that perform a specific task
  • They help you avoid code repetition and keep your code clean
  • Function anatomy:
    • def keyword, name, parameters, body, return, and indentation
    • Positional vs keyword arguments
    • Default values
  • Global vs local variables:
    • Local variables are only accessible inside the function
    • Global variables can be accessed from anywhere
    • Use global keyword to change global variables inside functions
  • Best practices:
    • Naming conventions
    • Docstrings
    • Indentation
    • Return vs print
  • Pitfalls to avoid:
    • Indentation errors
    • Missing return statements
    • Global vs local variables
    • Using print instead of return
    • Using return inside loops or if statements
    • Mixing spaces and tabs

But wait, there’s more! 😅

Further reading

Thank you very much! 🙏

Appendix 01: Area of a rectangle

  • Here’s the solution to the exercise:
def calculate_area(length, width):
    area = length * width
    return area

result = calculate_area(5, 4)
print(f"The area is: {result}")
The area is: 20

Back to the function

Appendix 02: Travel distance

  • Here’s the solution to the exercise:
distance_function = lambda speed, time: speed * time

distance = distance_function(100, 2)
print(f"The distance is: {distance}")
The distance is: 200

Back to the function